package emulator.hardware.nmos6502.commands;

import emulator.EmulatorException;
import emulator.hardware.HwByte;
import emulator.hardware.nmos6502.Command;
import emulator.hardware.nmos6502.CommandSet;
import emulator.hardware.nmos6502.Cpu6502;
import emulator.hardware.nmos6502.Flags6502;
import emulator.hardware.nmos6502.Operand;
import emulator.hardware.nmos6502.operands.*;

/** SBC (SuBtract with Carry)
 *  
 *  Affects Flags: S V Z C
 *  
 *  MODE           SYNTAX       HEX LEN TIM
 *  Immediate     SBC #$44      $E9  2   2
 *  Zero Page     SBC $44       $E5  2   3
 *  Zero Page,X   SBC $44,X     $F5  2   4
 *  Absolute      SBC $4400     $ED  3   4
 *  Absolute,X    SBC $4400,X   $FD  3   4+
 *  Absolute,Y    SBC $4400,Y   $F9  3   4+
 *  Indirect,X    SBC ($44,X)   $E1  2   6
 *  Indirect,Y    SBC ($44),Y   $F1  2   5+
 *  
 *  + add 1 cycle if page boundary crossed
 *  
 *  SBC results are dependent on the setting of the decimal flag. In decimal
 *  mode, subtraction is carried out on the assumption that the values
 *  involved are packed BCD (Binary Coded Decimal).
 *  
 *  There is no way to subtract without the carry which works as an inverse
 *  borrow. i.e, to subtract you set the carry before the operation. If the
 *  carry is cleared by the operation, it indicates a borrow occurred.
 */
public class SBC implements Command {

	@Override
	public void execute(Cpu6502 cpu, Operand operand) throws EmulatorException {
		Flags6502 flags = cpu.getFlags();
		int accu_old = (int) cpu.getAccu().getNumber();
		
		int op_in = (int) operand.getByte().getNumber();
		if (flags.getDecimal())
			accu_old = subDecimal(accu_old, flags, op_in);
		else
			accu_old = subBinary(accu_old, flags, op_in);
		
		cpu.setAccu(new HwByte(accu_old));
		cpu.setFlags(flags);
	}

	private int subDecimal(int accu_in, Flags6502 flags, int op_in) {
		int accu = 0;
		int borrow = (flags.getCarry() ? 0 : 1);
		
		int nibble1 = accu_in & 0x0F;
		int nibble2 = op_in & 0x0F;
		int sum = nibble1 - nibble2 - borrow;
		borrow = (sum < 0 ? 1 : 0);
		sum = sum % 10;
		if (sum < 0)
			accu += (10+sum);
		else
			accu += sum;

		nibble1 = (accu_in & 0xF0) >> 4;
		nibble2 = (op_in & 0xF0) >> 4;
		sum = nibble1 - nibble2 - borrow;
		borrow = (sum < 0 ? 1 : 0);
		sum = sum % 10;
		if (sum < 0)
			accu += (10+sum) << 4;
		else
			accu += sum << 4;

		flags.setCarry(borrow == 0);
		flags.setNZFromValue(accu);
		flags.setOverflow(false);

		return accu;
	}

	private int subBinary(int accu_in, Flags6502 flags, int op_in) {
		HwByte accu = new HwByte(accu_in);
		HwByte op_byte = new HwByte(op_in);
		op_byte.not();
		int op = (int) op_byte.getNumber();
		if (accu_in > 127) accu_in -= 256;
		if (op > 127) op -= 256;
		int signed_result = accu_in + op;
		if (flags.getCarry())
			signed_result++;
		int full = (int) (accu.getNumber() + op_byte.getNumber());
		if (flags.getCarry())
		{
			full++;
			op_byte.inc(1);
		}
		op_byte.add(accu);
		flags.setNZFromValue(op_byte);
		flags.setCarry(full>0xff);
		flags.setOverflow((signed_result < -128) || (signed_result > 127));
		return (int) op_byte.getNumber();
	}

	@Override
	public String getName() {
		return "SBC";
	}

	@Override
	public void register(CommandSet cset) {
		cset.defineCommand(0xE9, this, new Immediate());
		cset.defineCommand(0xE5, this, new ZeroPage());
		cset.defineCommand(0xF5, this, new ZeroPageX());
		cset.defineCommand(0xED, this, new Absolute());
		cset.defineCommand(0xFD, this, new AbsoluteX());
		cset.defineCommand(0xF9, this, new AbsoluteY());
		cset.defineCommand(0xE1, this, new IndirectX());
		cset.defineCommand(0xF1, this, new IndirectY());
	}

}
